package de.bht.fb6.cg1.exercise2;

import ij.IJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.gui.NewImage;
import ij.plugin.PlugIn;
import ij.process.ImageProcessor;

import java.awt.Point;
import java.util.HashSet;
import java.util.Set;

/**
 * This class represents a plug in for ImageJ. It diffues a image.
 * 
 * @author Stephan Rehfeld <rehfeld (-at-) beuth-hochschule.de >
 * @author Dominik Eckelmann
 */
public class Diffuser implements PlugIn {

    /**
     * The ImagePlus object of the loaded image.
     */
    private final ImagePlus imp;

    /**
     * This main method stars ImageJ and applies this plug in to the loaded image.
     * 
     * @param args
     *            Ignored.
     */
    public static void main(final String args[]) {
        IJ.open("test.jpg");
        final Diffuser rgbComponentRemover = new Diffuser(IJ.getImage());
        rgbComponentRemover.run("");
    }

    /**
     * This constructor creates a new Diffuser.
     * 
     * @param imp
     *            The ImagePlus object. Must not be null!
     */
    public Diffuser(final ImagePlus imp) {
        if (imp == null) {
            throw new IllegalArgumentException("The parameter 'imp' must not be null!");
        }
        this.imp = imp;
    }

    @Override
    public void run(final String arg) {
        imp.show();

        final GenericDialog gd = new GenericDialog("Remove Components");
        gd.addSlider("Runs", 1, 50, 1);
        gd.showDialog();

        final int runs = new Double(gd.getNextNumber()).intValue();

        final ImageProcessor ipOld = imp.getProcessor();
        final ImagePlus newImage = NewImage.createRGBImage("Skaliertes Bild", ipOld.getWidth(), ipOld.getHeight(), 1,
                NewImage.FILL_BLACK);

        final ImageProcessor ipNew = newImage.getProcessor();

        for (int x = 0; x < ipOld.getWidth(); x++) {
            for (int y = 0; y < ipOld.getHeight(); y++) {
                final Set<Integer> neighbours = getNeighbour(ipOld, new Point(x, y), runs);

                final int[] rvalue = new int[neighbours.size()];
                final int[] gvalue = new int[neighbours.size()];
                final int[] bvalue = new int[neighbours.size()];
                int c = 0;
                for (final Integer point : neighbours) {
                    rvalue[c] = (point & 0x00ff0000) >> 16;
                    gvalue[c] = (point & 0x0000ff00) >> 8;
                    bvalue[c] = point & 0x000000ff;
                    c++;
                }
                ipNew.set(x, y, (0xff << 24) | getMiddle(rvalue) << 16 | getMiddle(gvalue) << 8 | getMiddle(bvalue));
            }
        }

        newImage.show();
    }

    /**
     * get the color values of every neighbour.
     * 
     * example: n = neighbour x = position - irelevant pixel
     * 
     * deep 1:
     * 
     * ------------- -----nnn----- -----nxn----- -----nnn----- -------------
     * 
     * deep 2:
     * 
     * ----nnnnn---- ----nnnnn---- ----nnxnn---- ----nnnnn---- ----nnnnn----
     * 
     * @param ip
     *            image processor with the old image loaded
     * @param position
     *            position from wich the neighbours should be
     * @param deep
     *            distance to the highest neighbour
     * @return
     */
    private Set<Integer> getNeighbour(final ImageProcessor ip, final Point position, final int deep) {
        final Set<Integer> neighbour = new HashSet<Integer>();

        for (int x = (position.x - deep < 0) ? 0 : position.x - deep; x < position.x + deep && x < ip.getWidth(); x++) {
            for (int y = (position.y - deep < 0) ? 0 : position.y - deep; y < position.y + deep && y < ip.getHeight(); y++) {
                neighbour.add(ip.getPixel(x, y));
            }
        }

        return neighbour;
    }

    /**
     * calculates the middle value from a integer array.
     * 
     * @param vals
     *            integer values to calculate
     * @return middle value
     */
    private int getMiddle(final int[] vals) {
        int result = 0;
        for (final int i : vals) {
            result += i;
        }
        return result / vals.length;
    }

}
